/* Plot3DView.m   Copyright 1992 Steve Ludtke */
/* This view allows the display of 3d functions with real time rotation */

#import "Plot3DView.h"
#import "PlotShape.h"
#import "PControl.h"
#import <appkit/appkit.h>
#import <3Dkit/3Dkit.h>
#import <dpsclient/event.h>
#import <dpsclient/psops.h>
#import <stdio.h>
#import <math.h>
#include <libc.h>
#import "Expression.h"
#import "DensView.h"

void PSsethel();	/* makes Helvetica current font */

#define LRAD 10.0

extern id NXApp;

float rnd0(float x) { if (x<0) return(ceil(x)); else return floor(x); }

@implementation Plot3DView
-initFrame:(NXRect *)myrect
{
int i,j,ddl;
RtPoint fromP = {0,0,6.0}, toP = {0,0,0};
RtPoint lFromP = {5.0,10.0,5.0},lToP = {0,0,0};
RtMatrix mx = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1.0 };
id aShader;
char home[60];

[super initFrame:(NXRect *)myrect];

[self setBackgroundColor:NX_COLORWHITE];  
[self setDrawBackgroundColor:YES];

[self setEyeAt:fromP toward:toP roll:0.0];

mx[0][0]=mx[1][1]=mx[2][2]=3.0/fromP[2];
[self setPreTransformMatrix:mx];
  
  aShader=[[N3DShader alloc] init];
  [(N3DShader *)aShader setShader:"matte"];

  shape=[[PlotShape alloc] init];
  [(N3DShape *) shape  setShader:aShader];
  [shape scale:-1.0 :1.0 :1.0];
  [[self setWorldShape:shape] free];

  ambLight=[[N3DLight alloc] init];
  [ambLight makeAmbientWithIntensity:0.2];
  [self addLight:ambLight];
  ambient=0.2;

ltheta=.785;
lchi=-.785;
aLight=[[N3DLight alloc] init];
lFromP[0]= LRAD*sin(lchi)*cos(ltheta);
lFromP[2]= LRAD*cos(lchi)*cos(ltheta);
lFromP[1]= LRAD*sin(ltheta);
[aLight makeDistantFrom:lFromP to:lToP intensity:0.8];
[self addLight:aLight];

  Rmode=3;

ddl=[Window defaultDepthLimit];
if (ddl==NX_TwoBitGrayDepth||ddl==NX_EightBitGrayDepth) ddl=0;
else ddl=1;


/* initialize preferences */
for (i=0; i<MAXSETS; i++) {
	pref[i].ndata=0; 
	pref[i].sym= -1;

	if (ddl) {
		pref[i].mapcol[0][0]=0.0;
		pref[i].mapcol[0][1]=0.0;
		pref[i].mapcol[0][2]=0.5;

		pref[i].mapcol[1][0]=0.7;
		pref[i].mapcol[1][1]=0.0;
		pref[i].mapcol[1][2]=0.7;

		pref[i].mapcol[2][0]=1.0;
		pref[i].mapcol[2][1]=0.0;
		pref[i].mapcol[2][2]=0.0;

		pref[i].mapcol[3][0]=1.0;
		pref[i].mapcol[3][1]=0.3;
		pref[i].mapcol[3][2]=0.0;

		pref[i].mapcol[4][0]=1.0;
		pref[i].mapcol[4][1]=1.0;
		pref[i].mapcol[4][2]=0.0;
	}
	else {
		for (j=0; j<5; j++) {
			pref[i].mapcol[j][0]=pref[i].mapcol[j][1]=pref[i].mapcol[j][2]= 
				.5+(float)j/10.0;
		}
	}

	for (j=0; j<5; j++) pref[i].mapsel[j]=1;
	pref[i].mapmode=1;

	pref[i].fileData=pref[i].Sdata=NULL;
	pref[i].expr=[[Expression alloc] init];
	[pref[i].expr parse:"0"];
	pref[i].nx=pref[i].ny=10;
}
/* initial function to display */
pref[0].sym=5;
pref[0].nx=pref[0].ny=40;
[pref[0].expr parse:"cos(sqrt(x^2+y^2)+2*pi*t)/sqrt((x+a)^2+y^2+1)"];

/* start spinning, all angles in radians */
chi=.2;
theta=.433;
/*dchi=0.025;*/
dchi=0.0;
initflag=1;
Rflags=RF_axis+RF_persp;
Omode=0;
aspect=1.0;
[shape setOver:Omode];

if (ddl) {
	flagcol[0][0]=flagcol[4][0]=0.0;
	flagcol[0][1]=flagcol[4][1]=0.0;
	flagcol[0][2]=flagcol[4][2]=1.0;
	flagcol[1][0]=flagcol[2][0]=1.0;
	flagcol[1][1]=flagcol[2][1]=1.0;
	flagcol[1][2]=flagcol[2][2]=1.0;
	flagcol[3][0]=0.0;
	flagcol[3][1]=1.0;
	flagcol[3][2]=0.0;
}
else {
	flagcol[0][0]=flagcol[4][0]=0.5;
	flagcol[0][1]=flagcol[4][1]=0.5;
	flagcol[0][2]=flagcol[4][2]=0.5;
	flagcol[1][0]=flagcol[2][0]=1.0;
	flagcol[1][1]=flagcol[2][1]=1.0;
	flagcol[1][2]=flagcol[2][2]=1.0;
	flagcol[3][0]=0.0;
	flagcol[3][1]=0.0;
	flagcol[3][2]=0.0;
}
[shape setFlagColors:flagcol];

[[[NXApp printInfo] setVertCentered:YES] setOrientation:NX_PORTRAIT andAdjust:YES];
[[NXApp printInfo] setMarginLeft:0.0 right:0.0 top:0.0 bottom:0.0];
[[NXApp printInfo] setHorizPagination:NX_FITPAGINATION];
[[NXApp printInfo] setVertPagination:NX_FITPAGINATION];

timer=(DPSTimedEntry)DPSAddTimedEntry(TIMESTEP,itstime,self,NX_RUNMODALTHRESHOLD);
[self zoom:self];

if (getenv("USER")!=NULL) {
	sprintf(home,"/tmp/%s",getenv("USER"));
 	mkdir(home,0777);
}

return self;
}

/* fix the scaling after being resized */
-superviewSizeChanged:(const NXSize *)oldsize
{
[super superviewSizeChanged:oldsize];
[self zoom:self];
return self;
}

-free
{
DPSRemoveTimedEntry(timer);
[super free];
return self;
}

-drawPS:(NXRect *)myrect :(int)rectCount
{
char s[80],t[20];
int i,n,xyz[61];
float x,y,xpm,ypm,pv[61];
RtPoint pnt[61];

if (bounds.size.width<150) return self;
/* display alt and az in upper right corner */
PSsetgray(0.0);
PSsethel();
sprintf(s,"Alt:%6.2f  Az:%6.2f",theta*57.295787,chi*57.295787);
PSmoveto(bounds.size.width-100.0,bounds.size.height-15.0);
PSshow(s);
PSstroke();

if (Rflags&RF_labels && Rmode<4) {
	if (chi>(M_PI/2.0)&&chi<(M_PI*1.5)) ypm=1.1;
	else ypm=-1.1;
	if (chi<M_PI) xpm=1.1;
	else xpm=-1.1;

	for (n=0,y=Tick0[0],x=Tick00[0]; x<1; y+=Tick1[0],x+=Tick01[0],n++) { 
		if (n>59) n=59;
		pnt[n][0]=x; 
		pnt[n][1]=ypm;
		pnt[n][2]=-1.1; 
		pv[n]=y;
		xyz[n]=0;
	}
	for (y=Tick0[1],x=Tick00[1]; x<1; y+=Tick1[1],x+=Tick01[1],n++) { 
		if (n>59) n=59;
		pnt[n][1]=x; 
		pnt[n][0]=xpm;
		pnt[n][2]=-1.1; 
		pv[n]=y;
		xyz[n]=1;
	}
	for (y=Tick0[2],x=Tick00[2]; x<1; y+=Tick1[2],x+=Tick01[2],n++) { 
		if (n>59) n=59;
		pv[n]=y;
		pnt[n][2]=x; 
		if ((chi>M_PI/4.0&&chi<M_PI*.75)||(chi>M_PI*1.25&&chi<M_PI*1.75))
			{ pnt[n][1]=ypm; pnt[n][0]=-xpm; }
		else { pnt[n][1]=-ypm; pnt[n][0]=xpm; }
		xyz[n]=2;
	}
	pnt[n][0]=xpm/1.18; pnt[n][1]=ypm/1.18; pnt[n][2]=-1.0;

	[shape convertObjectPoints:pnt count:n+1 toCamera:self];
	x=pnt[n][0];
	for (i=0; i<n; i++) {
		if (i==0|| xyz[i]!=xyz[i-1]) 
			strcpy(t,[[tickpos cellAt:2 :xyz[i]] stringValue]);
		sprintf(s,t,pv[i]);
		if (pnt[i][0]<x) PSmoveto(pnt[i][0]-20.0,pnt[i][1]-5.0);
		else PSmoveto(pnt[i][0],pnt[i][1]-5.0);
		PSshow(s);
	}
	PSstroke();
}
 
return self;
}	

/* recalculates and redisplays data */
-zoom:sender
{
float x0,x1,xs,y0,y1,ys,xf,yf,zf;
float z0,z1,x,y,z,zz,T,A,B,C,D;
int i,j=0,k,cm,nc,color[5];
id tmp;

T=[[varT cellAt:0 :0] floatValue];
A=[[varT cellAt:1 :0] floatValue];
B=[[varT cellAt:2 :0] floatValue];
C=[[varT cellAt:3 :0] floatValue];
D=[[varT cellAt:4 :0] floatValue];

/* calculate minima, maxima and step sizes */
x0=minX;
x1=maxX;
y0=minY;
y1=maxY;

xf=(x1-x0)/2.0;
yf=(y1-y0)/2.0;

/* make sure there's enough space in the data array */
for (i=0; i<NFN; i++) {
	if (pref[i].sym==-1 || pref[i].fileData!=NULL) continue;
	if (pref[i].ndata!=(pref[i].nx*pref[i].ny)) {
		if (pref[i].ndata!=0) { free(pref[i].data); free(pref[i].color); }
		pref[i].data=malloc(sizeof(Point)*(pref[i].nx*pref[i].ny+2));
		pref[i].color=malloc(sizeof(RtColor)*(pref[i].nx*pref[i].ny+2));
		pref[i].ndata=pref[i].nx*pref[i].ny;
	}
}
z0=MAXFLOAT;
z1= -MAXFLOAT;

/* calculate (function mode) or clip (file mode) the data */
/* also finds min/max Z */
for (i=0; i<NFN; i++) {
	if (pref[i].sym==-1) continue;
	tmp=pref[i].expr;
	[tmp setVar:"t" value:T];
	[tmp setVar:"a" value:A];
	[tmp setVar:"b" value:B];
	[tmp setVar:"c" value:C];
	[tmp setVar:"d" value:D];

/* FILE MODE */
	if (pref[i].fileData!=NULL) {
		k=0;
		for (j=0; j<pref[i].nfdata; j++) {
			if (pref[i].fileData[j].x>x1||pref[i].fileData[j].x<x0||
			    pref[i].fileData[j].y>y1||pref[i].fileData[j].y<y0) continue;
			pref[i].data[k].x=(pref[i].fileData[j].x-x0)/xf-1.0;
			[tmp setVar:"x" value:pref[i].data[k].x];
			pref[i].data[k].y=(pref[i].fileData[j].y-y0)/yf-1.0;
			[tmp setVar:"y" value:pref[i].data[k].y];
			[tmp setVar:"z" value:pref[i].fileData[j].z];
			pref[i].data[k].z=[tmp resultValue];
			if (pref[i].data[k].z>z1) z1=pref[i].data[k].z;
			if (pref[i].data[k].z<z0) z0=pref[i].data[k].z;
			k++;
		}
		pref[i].ndata=k;

		if (pref[i].data[0].y!=pref[i].data[1].y) {
			pref[i].nx=pref[i].ny=(int)floor(sqrt((float)k));
		}
		else {
			for (j=1; j<k; j++) if (pref[i].data[j-1].y!=pref[i].data[j].y) break;
			pref[i].nx=j;
			pref[i].ny=k/j;
		}
	}
/* FUNCTION MODE */
	else {
		xs=(maxX-x0)/(float)(pref[i].nx-1);
		ys=(maxY-y0)/(float)(pref[i].ny-1);	
		x1=maxX+xs/2.0;
		y1=maxY+ys/2.0;
		j=0;
		for (y=y0; y<y1; y+=ys) {
			for (x=x0; x<x1; x+=xs) {
				pref[i].data[j].x=(x-x0)/xf-1.0;
				pref[i].data[j].y=(y-y0)/yf-1.0;
				[tmp setVar:"x" value:x];
				[tmp setVar:"y" value:y];
				zz=pref[i].data[j].z=[tmp resultValue];
				if (zz!=zz || zz<-MAXFLOAT || zz>MAXFLOAT) 
					zz=pref[i].data[j].z=0;
				if (zz>z1) z1=zz;
				if (zz<z0) z0=zz;
				j++;
			}
		}
	}
}
if (z0==MAXFLOAT) return self;

/* BOTH MODES */
if (z1<=z0) { z1=z0+.001; z0-=.001; }
/* allow the controller to override min/max Z */
[controller minmaxZ:&z0 :&z1];

/* tick spacing calculations */
if ([autotick intValue]) {
	[self tickCalc:5.0 :x0 :x1 :&Tick0[0] :&Tick1[0]];
	[[tickpos cellAt:0 :0] setFloatValue:Tick1[0]];
	[[tickpos cellAt:1 :0] setFloatValue:Tick0[0]];
	[self tickCalc:5.0 :y0 :y1 :&Tick0[1] :&Tick1[1]];
	[[tickpos cellAt:0 :1] setFloatValue:Tick1[1]];
	[[tickpos cellAt:1 :1] setFloatValue:Tick0[1]];
	[self tickCalc:5.0 :z0 :z1 :&Tick0[2] :&Tick1[2]];
	[[tickpos cellAt:0 :2] setFloatValue:Tick1[2]];
	[[tickpos cellAt:1 :2] setFloatValue:Tick0[2]];
}
else {
	Tick1[0]=[[tickpos cellAt:0 :0] floatValue];
	Tick0[0]=[[tickpos cellAt:1 :0] floatValue];
	Tick1[1]=[[tickpos cellAt:0 :1] floatValue];
	Tick0[1]=[[tickpos cellAt:1 :1] floatValue];
	Tick1[2]=[[tickpos cellAt:0 :2] floatValue];
	Tick0[2]=[[tickpos cellAt:1 :2] floatValue];
}
Tick00[0]=(Tick0[0]-x0)/(x1-x0)*2.0-1.0;
Tick01[0]=Tick1[0]/(x1-x0)*2.0;
Tick00[1]=(Tick0[1]-y0)/(y1-y0)*2.0-1.0;
Tick01[1]=Tick1[1]/(y1-y0)*2.0;
Tick00[2]=(Tick0[2]-z0)/(z1-z0)*2.0-1.0;
Tick01[2]=Tick1[2]/(z1-z0)*2.0;
[shape setTicks:Tick00 :Tick01];

zf=(z1-z0)/2.0;
/* scale and clip Z */
for (i=0; i<NFN; i++) {
	if (pref[i].sym==-1) continue;
	
	/* Spherical coordinates */
	if (pref[i].sym==6) {
		if (pref[i].Sdata!=NULL) free(pref[i].Sdata);
		pref[i].Sdata=malloc(sizeof(Point)*pref[i].ndata);
		for (k=0; k<pref[i].ndata; k++) {
			x=(pref[i].data[k].x+1.0)*xf+x0;
			y=(pref[i].data[k].y+1.0)*yf+y0;
			if (fabs(z0)>fabs(z1)) z=pref[i].data[k].z/fabs(z0);
			else z=pref[i].data[k].z/fabs(z1);
			if (z>1.0) z=1.0;
			if (z<-1.0) z=-1.0;
			pref[i].Sdata[k].x=z*cos(y)*sin(x);
			pref[i].Sdata[k].y=z*sin(y)*sin(x);
			pref[i].Sdata[k].z=z*cos(x);
		}
	}

	/* color info */
	cm=pref[i].mapmode;
	for (j=k=0; j<5; j++) if (pref[i].mapsel[j]) { color[k]=j; k++; }
	nc=k;
	if (nc<2) cm=0;

	for (k=0; k<pref[i].ndata; k++) {
		if (pref[i].data[k].z>z1) pref[i].data[k].z=z1;
		if (pref[i].data[k].z<z0) pref[i].data[k].z=z0;
		z=pref[i].data[k].z=(pref[i].data[k].z-z0)/zf-1.0;
		if (cm==1) {
			j=floor((z+1.0)/2.00001*(float)(nc-1));
			z=(z+1.0)/2.0*(float)(nc-1)-(float)j;
			pref[i].color[k][0]=z*pref[i].mapcol[color[j+1]][0]+
				(1.0-z)*pref[i].mapcol[color[j]][0];
			pref[i].color[k][1]=z*pref[i].mapcol[color[j+1]][1]+
				(1.0-z)*pref[i].mapcol[color[j]][1];
			pref[i].color[k][2]=z*pref[i].mapcol[color[j+1]][2]+
				(1.0-z)*pref[i].mapcol[color[j]][2];
		}
	}
}

/* display 3d plot */
[shape setData:pref :Rmode :Rflags];
[shape setAng:theta :chi :aspect :ambient];
[self display];
/* display density plot */
[controller updDen:Tick00 :Tick01];
return self;
}

/* obvious */
-setAng:(float)alt :(float)az
{
theta=alt;
chi=az;
return self;
}

/* allows spinning and zooming with the mouse */
-mouseDown:(NXEvent *)oevent 
{
int oldMask,loop=1;
float ix=0,iy=0,ix2=0,iy2=0;
NXEvent *event,evs;
long tm,tm2;

evs=*oevent;
oevent=&evs;
[self convertPoint:&oevent->location fromView:nil];
oevent->location.x=oevent->location.x/bounds.size.width*2.0-1.0;
oevent->location.y=oevent->location.y/bounds.size.height*2.0-1.0;
ix2=ix=oevent->location.x;
iy2=iy=oevent->location.y;
tm2=tm=oevent->time;

oldMask = [window addToEventMask:NX_LMOUSEDRAGGEDMASK];

while (loop) {
    event = [NXApp getNextEvent:(NX_LMOUSEUPMASK | NX_LMOUSEDRAGGEDMASK)];
    [self convertPoint:&event->location fromView:nil];
	event->location.x=event->location.x/bounds.size.width*2.0-1.0;
	event->location.y=event->location.y/bounds.size.height*2.0-1.0;

        switch (event->type) {
        case NX_LMOUSEUP:
                loop = 0;
	        	dchi=-(event->location.x-ix2)/(float)(event->time-tm2)*15.0;
				if (fabs(dchi)<.004) dchi=0.0;
				[shape setAng:theta :chi :aspect :ambient];
                [self display];
            break;
        case NX_LMOUSEDRAGGED:
	        theta-=(event->location.y-iy)*3.0;
	        chi-=(event->location.x-ix)*4.0;
	        if (theta>M_PI/2.0) theta=M_PI/2.0;
	        if (theta<-M_PI/2.0) theta=-M_PI/2.0;
	        while (chi>2.0*M_PI) chi-=2.0*M_PI;
	        while (chi<0.0) chi+=2.0*M_PI;
	        ix2=ix;
	        iy2=iy;
	        tm2=tm;
	        ix=event->location.x;
	        iy=event->location.y;
	        tm=event->time;
			[shape setAng:theta :chi :aspect :ambient];
	        [self display];
            break;
        default:
            break;
        }
}
[window setEventMask:oldMask];
return self;
}

/* function called by timer */
void itstime(DPSTimedEntry entry,double now,id call)
{
[call step];
return;
}

/* do one time step */
-step
{
if (initflag) { 
	/* first time, do initialization stuff */
	if (controller==nil) return self;
	[controller startup:pref];
	[self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];
	[self zoom:self];
	initflag=0; 
}
if (dchi==0.0) return self;
chi+=dchi;
while (chi>2.0*M_PI) chi-=2.0*M_PI;
while (chi<0.0) chi+=2.0*M_PI;
[shape setAng:theta :chi :aspect :ambient];
[self display];
return self;
}

-tickCalc:(float)n :(float)x0 :(float)x1 :(float *)min :(float *)spa
{
float s,e;

s=(x1-x0)/n;
e=floor(log10(s));
if (s/pow(10.0,e)<3.0) s=floor(s/pow(10.0,e-1.0))*pow(10.0,e-1.0);
else s=floor(s/pow(10.0,e))*pow(10.0,e);
*min=(floor(x0/s)+1.0)*(s);
*spa=s;
return self;
}

/* new min/max values */
-zoomTo:(float)minx :(float)miny :(float)maxx :(float)maxy
{
minX=minx;
minY=miny;
maxX=maxx;
maxY=maxy;
return self;
}

-(int)acceptsFirstMouse { return (YES); }

-setcontroller:con 
{
controller=con;
return self;
}

/* allow user to pause spinning */
-togFreeze:sender
{
static float Tdchi;

if ([sender intValue]) {
	Tdchi=dchi;
	dchi=0.0;
	return self;
}
dchi=Tdchi;
return self;
}

- renderSelf:(RtToken)context
{

return self;
}

-makeSMap:sender
{
FILE *out;
char home[60],s[80];
id image,rep;
NXSize size = { 400.0,400.0 };
NXStream *stream;
float xs,ys,x,y,xpm,ypm,fs;
int r;

sprintf(home,"/tmp/%s",getenv("USER"));
if (getenv("USER")==NULL) strcpy(home,"/tmp"); 

/* write maps for axis labels */
if (Rflags&RF_labels) {
	[window setTitle:"Generating Axis Label Maps"];

	if (chi>(M_PI/2.0)&&chi<(M_PI*1.5)) ypm=1.0;
	else ypm=-1.0;
	if (chi<M_PI) xpm=1.0;
	else xpm=-1.0;

	r=floor(chi/M_PI*4.0);

	image=[[NXImage alloc] initSize:&size];
	[image setCacheDepthBounded:NO];
	[image useCacheWithDepth:NX_TwentyFourBitRGBDepth];
	rep=[image lastRepresentation];
	[rep setAlpha:NO];
	[rep setNumColors:3];
	[image lockFocus];
	PSselectfont("Helvetica-Bold",fs=[fontSize floatValue]);
	PSsetrgbcolor(1.0,.95,1.0);
	PSmoveto(0,0);
	PSlineto(1.0,1.0);
	PSstroke();

	PSsetrgbcolor(0,0,0);
	strcpy(s,[[axisTitle cellAt:0 :0] stringValue]);
	PSstringwidth(s,&xs,&ys);
	PSmoveto((size.width-xs)/2.0,size.height/4.0-fs*2.0-10.0);
	PSshow(s);
	for (y=Tick0[0],x=Tick00[0]; x<1; y+=Tick1[0],x+=Tick01[0]) { 
		sprintf(s,[[tickpos cellAt:2 :0] stringValue],y);
		PSstringwidth(s,&xs,&ys);
		if (ypm<0)
			PSmoveto((x+1.0)*size.width/2.0-xs/2.0-1.0,size.height/4.0-fs-6.0);
		else PSmoveto(size.width*(1.0-x)/2.0-xs/2.0-1.0,size.height/4.0-fs-6.0);
		PSshow(s);
	}
	PSstroke();

	strcpy(s,[[axisTitle cellAt:1 :0] stringValue]);
	PSstringwidth(s,&xs,&ys);
	PSmoveto((size.width-xs)/2.0,size.height/2.0-fs*2.0-10.0);
	PSshow(s);
	for (y=Tick0[1],x=Tick00[1]; x<1; y+=Tick1[1],x+=Tick01[1]) { 
		sprintf(s,[[tickpos cellAt:2 :1] stringValue],y);
		PSstringwidth(s,&xs,&ys);
		if (xpm>0) 
			PSmoveto((x+1.0)*size.width/2.0-xs/2.0-1.0,size.height/2.0-fs-6.0);
		else PSmoveto(size.width*(1.0-x)/2.0-xs/2.0-1.0,size.height/2.0-fs-6.0);
		PSshow(s);
	}
	PSstroke();

	PSgsave();
	strcpy(s,[[axisTitle cellAt:2 :0] stringValue]);
	PSstringwidth(s,&xs,&ys);
	PStranslate(size.width/2.0,size.height*3.0/4.0);
	if (r%2==1) PSrotate(180.0);
	PSmoveto(-xs/2.0,0.0);
	PSshow(s);
	PSstroke();
	PSgrestore();
	PSgsave();
	PSrotate(90.0);
	PStranslate(size.height/2.0,-size.width);
	for (y=Tick0[2],x=Tick00[2]; x<1; y+=Tick1[2],x+=Tick01[2]) { 
		sprintf(s,[[tickpos cellAt:2 :2] stringValue],y);
		PSstringwidth(s,&xs,&ys);
		if (r%2==1)
			PSmoveto(size.height/2.0-xs-2.0,(x+1.0)*(size.width)/2.0-8.0);
		else PSmoveto(8.0,(x+1.0)*(size.width)/2.0-8.0);
		PSshow(s);
	}
	PSstroke();
	PSgrestore();
	[image unlockFocus];
	stream=NXOpenMemory(NULL,0,NX_WRITEONLY);
	[image writeTIFF:stream];
	sprintf(s,"%s/plot3dxyz.tiff",home);
	NXSaveToFile(stream,s);
	NXClose(stream);
	[image free];

	sprintf(s,"%s/plot3d.rib",home);
	out=fopen(s,"w");
	fprintf(out,"MakeTexture \"%s/plot3dxyz.tiff\" \"%s/plot3dxyz.tx\" \"clamp\" \"black\" \"box\" 1 1\n",home,home);
	fclose(out);
	sprintf(s,"/usr/prman/prman %s/plot3d.rib",home);
	system(s);
	[window setTitle:"3D"];
}

if (Omode==0||Omode==OVER_csurf) return self;
[window setTitle:"Generating Surface Map"];

/* make map of contour/density plot */
[controller dumpContour];
sprintf(s,"%s/plot3d.rib",home);
out=fopen(s,"w");
fprintf(out,"MakeTexture \"%s/plot3d.tiff\" \"%s/plot3d.tx\" \"black\" \"black\" \"gaussian\" 1.8 1.8\n",home,home);
fclose(out);
sprintf(s,"/usr/prman/prman %s/plot3d.rib",home);
system(s);
[window setTitle:"3D"];
return self;
}

- dumpRib:sender
{
  static id savePanel=nil;
  NXStream *ts;
  char buf[MAXPATHLEN+1];

  if (!savePanel) {
    savePanel=[SavePanel new];
    [savePanel setRequiredFileType:"rib"];
  }

  Rmode+=4;
  [shape setData:pref :Rmode :Rflags];

  if([savePanel runModal]){
 	 [self makeSMap:self];
    ts=NXOpenMemory(NULL, 0, NX_WRITEONLY);
	strcpy(buf, [savePanel filename]);
    strrchr(buf,'.')[0]='\0';
    NXPrintf(ts, "Display \"%s.tiff\" \"file\" \"rgba\"\n", buf);
    [self copyRIBCode:ts];
	NXSaveToFile(ts, [savePanel filename]);
    NXCloseMemory(ts,NX_FREEBUFFER);
  }

  Rmode-=4;
  [shape setData:pref :Rmode :Rflags];
return self;
}

-PRTiff:sender
{
static id savePanel=nil;

if (!savePanel) {
	savePanel=[SavePanel new];
	[savePanel setRequiredFileType:"tiff"];
}

if([savePanel runModal]) [self renderTIFF:[savePanel filename]];

return self;
}

- renderTIFF:(char *)fsp
{
NXStream *ts;
char buf[MAXPATHLEN+1];
char home[60];

sprintf(home,"/tmp/%s",getenv("USER"));
if (getenv("USER")==NULL) strcpy(home,"/tmp"); 


Rmode+=4;
[shape setData:pref :Rmode :Rflags];

[self makeSMap:self];
[window setTitle:"Rendering TIFF"];
ts=NXOpenMemory(NULL, 0, NX_WRITEONLY);
NXPrintf(ts, "Display \"%s\" \"file\" \"rgba\"\n", fsp);
[self copyRIBCode:ts];
sprintf(buf,"%s/plot3dp.rib",home);
NXSaveToFile(ts, buf);
NXCloseMemory(ts,NX_FREEBUFFER);


[shape setData:pref :Rmode :Rflags];
Rmode-=4;

sprintf(buf,"/usr/prman/prman %s/plot3dp.rib",home);
system(buf);

[window setTitle:"3D"];

return self;
}

- setAmbLight:sender
{
[ambLight setIntensity:ambient=[sender floatValue]];
[self display];
return self;
}

- setLight:sender
{
[aLight setIntensity:[sender floatValue]];
[self display];
return self;
}

-setLightX:sender
{
RtPoint from;
lchi= -[sender floatValue];
from[0]= LRAD*sin(lchi)*cos(ltheta);
from[2]= LRAD*cos(lchi)*cos(ltheta);
from[1]= LRAD*sin(ltheta);
[aLight setFrom:from];
[self display];
return self;
}

-setLightY:sender
{
RtPoint from;
ltheta=[sender floatValue];
from[0]= LRAD*sin(lchi)*cos(ltheta);
from[2]= LRAD*cos(lchi)*cos(ltheta);
from[1]= LRAD*sin(ltheta);
[aLight setFrom:from];
[self display];
return self;
}

- setMode:sender
{
int i;

Rmode=i=[sender selectedRow];
switch(i) {
case 0:
 [self setSurfaceTypeForAll:N3D_PointCloud chooseHider:YES];
 [window setDepthLimit:NX_TwoBitGrayDepth];
break;
case 1:
 [self setSurfaceTypeForAll:N3D_WireFrame chooseHider:YES];
 [window setDepthLimit:NX_TwoBitGrayDepth];
break;
case 2:
 [self setSurfaceTypeForAll:N3D_FacetedSolids chooseHider:YES];
break;
case 3:
 [self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];
break;
}
[shape setData:pref :Rmode :Rflags];
[self display];
return self;
}

-setOverlay:sender
{
Omode=0;
if ([[sender cellAt:0 :0] intValue]) Omode+=OVER_surf;
if ([[sender cellAt:1 :0] intValue]) Omode+=OVER_csurf;
if ([[sender cellAt:2 :0] intValue]) Omode+=OVER_base;

[shape setOver:Omode];
return self;
}

-printPSCode:sender
{

[self makeSMap:self];
Rmode+=4;
[shape setData:pref :Rmode :Rflags];
[super printPSCode:sender];
Rmode-=4;
[shape setData:pref :Rmode :Rflags];
return self;
}

-setFlags:sender
{
Rflags=0;
if ([[flagSel findCellWithTag:0] intValue]) Rflags+=RF_axis;
if ([[flagSel findCellWithTag:1] intValue]) Rflags+=RF_backs;
if ([[flagSel findCellWithTag:2] intValue]) Rflags+=RF_floor;
if ([[flagSel findCellWithTag:3] intValue]) Rflags+=RF_ticks;
if ([[flagSel findCellWithTag:4] intValue]) Rflags+=RF_planes;
if ([[flagSel findCellWithTag:5] intValue]) Rflags+=RF_labels;
[shape setData:pref :Rmode :Rflags];
[self display];
return self;
}

-setFlagColor:sender
{
int i;

i=[sender tag];
NXConvertColorToRGB([sender color], 
	&flagcol[i][0],&flagcol[i][1],&flagcol[i][2]);
[shape setFlagColors:flagcol];
[self display];
return self;
}

-size320:sender
{
[[self window] sizeWindow:325 :204];
return self;
}

-setEye:sender
{
RtPoint fromP = {0,0,6.0},toP={0.0,0.0,0.0};
RtMatrix mx = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1.0 };

fromP[2]=[sender floatValue];
[self setEyeAt:fromP toward:toP roll:0.0];
mx[0][0]=mx[1][1]=mx[2][2]=3.0/fromP[2];
[self setPreTransformMatrix:mx];
[self display];
return self;
}

-setAsp:sender
{
aspect=[sender floatValue];
[shape setAng:theta :chi :aspect :ambient];
[self display];
return self;
}

-setProj:sender
{
if ([sender intValue]) {
	[self setProjection:N3D_Perspective];
	Rflags|=RF_persp;
	[self setUsePreTransformMatrix:NO];
}
else {
	[self setProjection:N3D_Orthographic];
	Rflags&=(~RF_persp);
	[self setUsePreTransformMatrix:YES];
}
[self display];
return self;
}

-(float)getTheta
{
return(theta);
}

-(float)getChi
{
return(chi);
}

-aspect11:sender
{
[aspectS setFloatValue:1.0];
aspect=1.0;
[shape setAng:theta :chi :aspect :ambient];
[self display];
return self;
}
@end
